package se.webbzon.boltzmann.game.object.dialogue;

import java.awt.Color;
import java.awt.Dimension;
import java.util.List;

import javax.vecmath.Vector3d;

import se.webbzon.boltzmann.game.object.dialogue.DialogueArrow.Direction;
import se.webbzon.boltzmann.game.object.dialogue.layout.IntegerDialogueLayout;
import se.webbzon.boltzmann.game.object.dialogue.word.AbstractDialogueWord;
import se.webbzon.boltzmann.game.object.dialogue.word.ColoredDialogueWord;
import se.webbzon.boltzmann.game.sequence.Sequences;
import se.webbzon.boltzmann.input.Button;
import se.webbzon.boltzmann.input.Input;
import se.webbzon.oschi01.worldarea.WorldArea;
import se.webbzon.oschi01.worldobject.appearance.SimpleGroupAppearance3D;

public class IntegerDialogue extends AbstractDialogue {
	
	// The strings containing the minimal and maximal value
	private final String min, max;
	
	// The word representing the selected value
	private final ColoredDialogueWord valueWord;
	
	// The items of the dialogue
	private final AbstractDialogueItem circle, upArrow, downArrow;
	
	// The prefix and postfix words to the value word
	private final List<AbstractDialogueWord> prefix, postfix;
	
	// The minimal and maximal value of this integer dialogue
	private int minValue, maxValue;
		
	// The maximal radix of this integer dialogue
	private int maxRadix;
	
	// The selected value of this integer dialogue
	private volatile int value;
	
	// The selected radix of this integer dialogue 
	private volatile int radix;

	// Calls all the listeners in the next step if set
	private volatile boolean proceed;

	// Shows all the words in the next step if set
	private volatile boolean showAll;
	
	/*============================================================
	Constructors
	============================================================*/
	
	/** Creates a new integer dialogue with the given layout, background
	 * and text. A positive minimal and maximal value must be provided. **/
	public IntegerDialogue(	IntegerDialogueLayout layout, 
							DialogueBackground background, 
							String text, 
							String prefix, String postix,
							String minValue, String maxValue) {
		this(	layout, background, 
				DialogueTextParser.parse(layout, text), 
				DialogueTextParser.parse(layout, prefix),
				DialogueTextParser.parse(layout, postix),
				minValue, maxValue);
	}
	
	/** Creates a new integer dialogue with the given layout, background
	 * and list of words. A positive minimal and maximal value must 
	 * be provided. **/
	public IntegerDialogue(	IntegerDialogueLayout layout, 
							DialogueBackground background, 
							List<AbstractDialogueWord> words,
							List<AbstractDialogueWord> prefix,
							List<AbstractDialogueWord> postfix,
							String minValue, String maxValue) {
		super(layout, background, words);
		this.prefix = prefix;
		this.postfix = postfix;
		this.min = minValue;
		this.max = maxValue;
		this.minValue = 0;
		this.maxValue = 0;
		this.maxRadix = 0;
		value = 0;
		radix = 0;
		proceed = false;
		showAll = false;
		
		// Create dialogue items
		valueWord = new ColoredDialogueWord(layout,"",Color.gray);
		circle = new DialogueCircle(layout.getCircleRadius());
		upArrow = new DialogueArrow(Direction.UP,2);
		downArrow = new DialogueArrow(Direction.DOWN,2);
		final double w = layout.getCharacterWidth();
		valueWord.showAllCharacters(this);
		valueWord.hide();
		circle.setPosition(
				layout.getWidth()/2 - layout.getCircleRadius()*2, 
				layout.getCircleYSpacing(), 
				0);
		circle.hide();
		upArrow.setSize(w,w,1);
		upArrow.hide();
		downArrow.setSize(w,w,1);
		downArrow.hide();
		
		// Add items to dialogue
		SimpleGroupAppearance3D appearance = 
				(SimpleGroupAppearance3D) getAppearance();
		appearance.addAppearance(valueWord.getAppearance());
		appearance.addAppearance(upArrow.getAppearance());
		appearance.addAppearance(downArrow.getAppearance());
		appearance.addAppearance(circle.getAppearance());
		for (AbstractDialogueWord word : prefix) {
			appearance.addAppearance(word.getAppearance());
			word.showAllCharacters(this);
			word.hide();
		}
		for (AbstractDialogueWord word : postfix) {
			appearance.addAppearance(word.getAppearance());
			word.showAllCharacters(this);
			word.hide();
		}
	}
	
	/*============================================================
	Public Methods
	============================================================*/
	
	/** Returns the minimal value which can be selected in the
	 * integer dialogue. **/
	public int getMinValue() {
		return minValue;
	}
	
	/** Returns the maximal value which can be selected in the 
	 * integer dialogue. **/
	public int getMaxValue() {
		return maxValue;
	}
	
	/** Returns the current value of the integer dialogue. **/
	public int getValue() {
		return value;
	}
	
	@Override public void onStep(WorldArea world) {
		super.onStep(world);
		if (proceed) {
			proceed = false;
			notifyOnContinue();
		} else if (showAll) {
			showAll = false;
			showAllWords();
		}
	}

	@Override public void primaryAxisChanged(Input input, double direction, double value) {};

	@Override public void secondaryAxisChanged(Input input, double direction, double value) {};

	@Override public void buttonPressed(Input input, Button button) {
		if (isInputReady()) {
			switch (button) {
			case ACTION:
				if (isAllVisible())
					proceed = true;
				else
					showAll = true;
				break;
			case UP:
				value += (int) Math.pow(10, radix);
				if (value > maxValue)
					value = maxValue;
				updateValue();
				break;
			case DOWN:
				value -= (int) Math.pow(10, radix);
				if (value < minValue)
					value = minValue;
				updateValue();
				break;
			case LEFT:
				if (radix < maxRadix)
					radix++;
				updateRadix();
				break;
			case RIGHT:
				if (radix > 0)
					radix--;
				updateRadix();
				break;
			}
		}
	}

	@Override public void buttonReleased(Input input, Button button) {};
	
	@Override public void bindingAccepted(Input input, Button button) {};

	@Override public void onShowInput() {
		Object min = Sequences.parse(getSequence(), this.min);
		Object max = Sequences.parse(getSequence(), this.max);
		this.minValue = (min instanceof Double) ? (int) (double) min : 0;
		this.maxValue = 
				Math.max(	this.minValue, 
							(max instanceof Double) ? (int) (double) max : 100);
		// Calculate the maximal radix
		if (this.maxValue == 0 && this.minValue == 0)
			maxRadix = 0;
		else {
			final double maxAbsValue = 
					Math.max(Math.abs(this.maxValue), Math.abs(this.minValue));
			maxRadix = (int) Math.floor(Math.log10(maxAbsValue));
		}
		value = minValue;
		radix = 0;
		
		updateValue();
		
		updateLayout();
		for (AbstractDialogueWord word : prefix)
			word.display();
		for (AbstractDialogueWord word : postfix)
			word.display();
		valueWord.display();
		upArrow.display();
		downArrow.display();
		circle.display();
		
		updateRadix();
	}

	@Override public void onHideInput() {
		for (AbstractDialogueWord word : prefix)
			word.hide();
		for (AbstractDialogueWord word : postfix)
			word.hide();
		valueWord.hide();
		upArrow.hide();
		downArrow.hide();
		circle.hide();
	}
	
	/*============================================================
	Private Methods
	============================================================*/
	
	/** Updates the layout of the dialogue. **/
	private void updateLayout() {
		IntegerDialogueLayout layout = (IntegerDialogueLayout) getLayout();
		final Dimension d = new Dimension();
		final double y = layout.getCounterYOffset();
		double x = - layout.getWidth()/2 + layout.getTextXOffset();
		
		// Position all the prefix words
		for (AbstractDialogueWord word : prefix) {
			word.getDimension(d);
			word.onUpdateWord(this);
			word.setPosition(x, y, 0);
			x += d.getWidth();
		}
		
		// Position the value word
		x += layout.getCharacterWidth();
		valueWord.getDimension(d);
		valueWord.setPosition(x, y, 1);
		x += d.getWidth() + layout.getCharacterWidth();
		
		// Position all the postfix words
		for (AbstractDialogueWord word : postfix) {
			word.getDimension(d);
			word.onUpdateWord(this);
			word.setPosition(x, y, 0);
			x += d.getWidth();
		}
	}
	
	/** Updates the text in the dialogue word which presents the
	 * value of the integer dialogue. **/
	private void updateValue() {
		final int length;
		if (value == 0)
			length = maxRadix;
		else
			length = maxRadix - (int) Math.floor(Math.log10(Math.abs(value)));
		String zeros = new String(new char[length]).replace("\0", "0");
		if (minValue < 0) {
			if (value < 0)
				valueWord.setWord("-" + zeros + (-value));
			else
				valueWord.setWord(" " + zeros + value);
		} else
			valueWord.setWord(zeros + value);
		valueWord.showAllCharacters(this);
	}
	
	/** Updates the radix by moving the dialogue arrows in 
	 * the integer dialogue. **/
	private void updateRadix() {
		IntegerDialogueLayout layout = (IntegerDialogueLayout) getLayout();

		Vector3d position = new Vector3d();
		valueWord.getPosition(position);
		final double xOffset = (minValue < 0) ? 1.5 : 0.5;
		final double x = position.x + 
							layout.getCharacterWidth() * 
							(xOffset + maxRadix - radix);
		final double y = position.y - layout.getCharacterWidth()/2;
		final double spacing = layout.getCounterYSpacing();
		downArrow.setPosition(x, y - layout.getCharacterHeight() - spacing, 0);
		upArrow.setPosition(x, y + layout.getCharacterWidth() + spacing, 0);
	}
	
	/** Notifies all listeners to continue. **/
	private void notifyOnContinue() {
		for (DialogueListener listener : getListeners().toArray(new DialogueListener[0])) {
			if (listener instanceof IntegerDialogueListener)
				((IntegerDialogueListener) listener).onContinue(this, value);
		}
	}

}
